#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <linux/fb.h>

#define FB_DEV          "/dev/fb0"
#define V4L2_DEV        "/dev/video1"
#define FARMBUFFER      3

typedef struct{
    unsigned short *start;
    unsigned long  length;
}cam_buf_info;

static int fb_fd = -1;
static int v4l2_fd = -1;
static int width;
static int height;
static unsigned short *screen_base = NULL;
static int frm_width,frm_height;
struct v4l2_format vfmt;
unsigned long screen_size;
cam_buf_info bufinfo[FARMBUFFER];

static int fb_dev_init(void){
    struct fb_var_screeninfo fb_var = {0};
    struct fb_fix_screeninfo fb_fix = {0};

    fb_fd = open(FB_DEV, O_RDWR);
    if(fb_fd < 0){
        fprintf(stderr,"open error: %s : %s\n",FB_DEV,strerror(errno));
        return -1;
    }
    ioctl(fb_fd,FBIOGET_VSCREENINFO,&fb_var);
    ioctl(fb_fd,FBIOGET_FSCREENINFO,&fb_fix);
    
    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;

    printf("screen_size = %d\r\n",screen_size);

    screen_base = mmap(NULL,screen_size,PROT_WRITE|PROT_READ,MAP_SHARED,fb_fd,0);
    if(MAP_FAILED == (void *)screen_base){
        perror("mmap error");
        close(fb_fd);
        return -1;
    }
    memset(screen_base,0x00,screen_size);
    return 0;
}

//设置摄像头参数
static int v4l2_dev_init(void)
{
    struct v4l2_capability cap = {0};

    v4l2_fd = open(V4L2_DEV,O_RDWR);
    if (0 > v4l2_fd) {
        fprintf(stderr, "open error: %s: %s\n", "/dev/video1", strerror(errno));
        close(v4l2_fd);
        return -1;
    }

    /* 查询设备功能 */
    ioctl(v4l2_fd, VIDIOC_QUERYCAP, &cap);

     /* 判断是否是视频采集设备 */
    if (!(V4L2_CAP_VIDEO_CAPTURE & cap.capabilities)) {
        fprintf(stderr, "Error: %s: No capture video device!\r\n", V4L2_DEV);
        close(v4l2_fd);
        return -1;
    }
    return 0;
}

static int v4l2_set_param(void)
{
    struct v4l2_streamparm streamparm = {0};
        // 设置视频格式
    memset(&vfmt, 0, sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vfmt.fmt.pix.width = width;
    vfmt.fmt.pix.height = height;
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; 
    vfmt.fmt.pix.field = V4L2_FIELD_NONE;

    int ret = ioctl(v4l2_fd, VIDIOC_S_FMT, &vfmt);
    if(ret < 0){
        perror("设置格式失败");
        close(v4l2_fd);
        return -1;
    }

    /*设置的视频格式是什么*/
    if(V4L2_PIX_FMT_RGB565 != vfmt.fmt.pix.pixelformat){
        fprintf(stderr, "Error: the device does not support RGB565 format!\n");
        close(v4l2_fd);
        return -1;
    }

    frm_width = vfmt.fmt.pix.width;  //获取实际的帧宽度
    frm_height = vfmt.fmt.pix.height;//获取实际的帧高度
    printf("视频帧大小<%d * %d>\r\n", frm_width, frm_height);

     /* 获取streamparm */
    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(v4l2_fd, VIDIOC_G_PARM, &streamparm);

    /** 判断是否支持帧率设置 **/
    if (V4L2_CAP_TIMEPERFRAME & streamparm.parm.capture.capability) {
        streamparm.parm.capture.timeperframe.numerator = 1;
        streamparm.parm.capture.timeperframe.denominator = 60;//30fps
        if (0 > ioctl(v4l2_fd, VIDIOC_S_PARM, &streamparm)) {
            fprintf(stderr, "ioctl error: VIDIOC_S_PARM: %s\n", strerror(errno));
            return -1;
        }
    }

    return 0;
}


static int v4l2_initbuffer(void)
{
    int ret = -1;

    struct v4l2_requestbuffers request_buffer;
    struct v4l2_buffer v4l2_buf;

    memset(&request_buffer,0,sizeof(request_buffer));
    request_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    request_buffer.count = FARMBUFFER;
    request_buffer.memory = V4L2_MEMORY_MMAP;

    ret = ioctl(v4l2_fd,VIDIOC_REQBUFS,&request_buffer);
    if(ret < 0){
        perror("申请内存失败\r\n");
        close(v4l2_fd);
        return -1;
    }

    v4l2_buf.type =  V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buf.memory = V4L2_MEMORY_MMAP;   

    for(v4l2_buf.index = 0; v4l2_buf.index < FARMBUFFER; v4l2_buf.index++){
        ret = ioctl(v4l2_fd,VIDIOC_QUERYBUF,&v4l2_buf);
        if(ret < 0){
            perror("申请结束\r\n");
            close(v4l2_fd);
        }
        bufinfo[v4l2_buf.index].length = v4l2_buf.length;
        bufinfo[v4l2_buf.index].start = mmap(NULL,v4l2_buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,v4l2_fd,v4l2_buf.m.offset);
        printf("buf%d = %x\r\n",v4l2_buf.index,bufinfo[v4l2_buf.index].start);
        if(MAP_FAILED == bufinfo[v4l2_buf.index].start){
            perror("mmap error\r\n");
            close(v4l2_fd);
            return -1;
        }
    }

    for(v4l2_buf.index = 0; v4l2_buf.index < FARMBUFFER; v4l2_buf.index++){
        if(0 > ioctl(v4l2_fd, VIDIOC_QBUF, &v4l2_buf)){
            fprintf(stderr, "ioctl error: VIDIOC_QBUF: %s\n", strerror(errno));
            close(v4l2_fd);
            return -1;
        }
    }

    return 0;
}

static int v4l2_start(void)
{
    int ret;
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(v4l2_fd,VIDIOC_STREAMON,&type);
    if(ret < 0){
        perror("开启流失败\r\n");
        close(v4l2_fd);
        return -1;
    }
    return 0;
}

static void v4l2_show(void)
{
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    struct v4l2_buffer v4l2_buf;
    int min_w, min_h;
    unsigned short *base;
    unsigned short *start;
    int j = 0;
    int i = 0;
    if (width > frm_width)
        min_w = frm_width;
    else
        min_w = width;
    if (height > frm_height)
        min_h = frm_height;
    else
        min_h = height;

    v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buf.memory = V4L2_MEMORY_MMAP;
    for(;;){
        for(v4l2_buf.index = 0; v4l2_buf.index < FARMBUFFER; v4l2_buf.index++){

            ioctl(v4l2_fd,VIDIOC_DQBUF,&v4l2_buf);
                for(j = 0, base = screen_base, start = bufinfo[v4l2_buf.index].start; 
                    j < min_h; j++){
                    memcpy(base,start,min_w * 2);
                    base += width;
                    start += frm_width;
                }
            ioctl(v4l2_fd,VIDIOC_QBUF,&v4l2_buf);
        }
    }
    // 关闭视频流
    if(ioctl(v4l2_fd, VIDIOC_STREAMOFF, &type) < 0){
        perror("关闭流失败\r\n");
        close(v4l2_fd);
    }

    // 解除映射
    for(i = 0; i < FARMBUFFER; i++) {
        if(bufinfo[i].start) {
            munmap(bufinfo[i].start, bufinfo[i].length);
        }
    }
    close(v4l2_fd);
}

int main(void)
{
    if(fb_dev_init()){
        printf("exit fb\r\n");
        exit(EXIT_FAILURE);
    }
    if(v4l2_dev_init()){
        printf("exit dev_init\r\n");
        exit(EXIT_FAILURE);
    }
    if(v4l2_set_param()){
        printf("exit set_param\r\n");
        exit(EXIT_FAILURE);
    }
    if(v4l2_initbuffer()){
        printf("exit initbuffer\r\n");
        exit(EXIT_FAILURE);
    }
    if(v4l2_start()){
        printf("exit start\r\n");
        exit(EXIT_FAILURE);
    }
    
    v4l2_show();
}






